<?php
/**
 * YGdImage.php
 *
 * @author Hua Yang <htmlcook@gmail.com>
 * @since 2.0
 */

Yii::import('common.components.image.YImageInterface');

/**
 * Class YGdImage
 */
class YGdImage implements YImageInterface
{
	private $_resource;

	public function __construct($resource)
	{
		$this->_resource = $resource;
	}

	public function __destruct()
	{
		if (is_resource($this->_resource) && 'gd' === get_resource_type($this->_resource)) {
			imagedestroy($this->_resource);
		}
	}

	/**
	 * @inheritdoc
	 */
	public function resize($width, $height)
	{
		$dest = $this->createImage(array($width, $height), 'resize');

		imagealphablending($this->_resource, true);
		imagealphablending($dest, true);

		if (false == imagecopyresampled($dest, $this->_resource, 0, 0, 0, 0,
				$width, $height, imagesx($this->_resource), imagesy($this->_resource)
			)
		) {
			throw new RuntimeException('Image resize operation failed');
		}

		imagealphablending($this->_resource, false);
		imagealphablending($dest, false);

		imagedestroy($this->_resource);

		$this->_resource = $dest;

		return $this;
	}

	/**
	 * @inheritdoc
	 */
	public function crop($width, $height, $left = 'center', $top = 'center')
	{
		$geometry = $this->sanitizeGeometry(array(
			'width' => $width,
			'height' => $height,
			'top' => $top,
			'left' => $left
		));

		$dest = $this->createImage(array($width, $height), 'crop');

		if (false === imagecopy($dest, $this->_resource, 0, 0,
				$geometry['left'], $geometry['top'], $width, $height)
		) {
			throw new RuntimeException('Image crop operation failed');
		}

		imagedestroy($this->_resource);

		$this->_resource = $dest;
		return $this;
	}

	/**
	 * @inheritdoc
	 */
	public function rotate($angle)
	{
		$resource = imagerotate($this->_resource, -1 * $angle, 0);

		if (false === $resource) {
			throw new RuntimeException('Image rotate operation failed');
		}

		imagedestroy($this->_resource);
		$this->_resource = $resource;

		return $this;
	}

	/**
	 * @inheritdoc
	 */
	public function copy()
	{
		list($width, $height) = $this->getSize();

		$copy = $this->createImage(array($width, $height), 'copy');

		if (false === imagecopy($copy, $this->_resource, 0, 0, 0, 0, $width, $height)) {
			throw new RuntimeException('Image copy operation failed');
		}

		return new YGdImage($copy);
	}

	public function flip($mode)
	{
		list($width, $height) = $this->getSize();
		$dest = $this->createImage(array($width, $height), 'flip');
		if ($mode == YImageInterface::IMG_FLIP_VERTICAL) {
			$sx = $width - 1;
			$sy = 0;
			$sw = -$width;
			$sh = $height;
		} elseif ($mode === YImageInterface::IMG_FLIP_HORIZONTAL) {
			$sx = 0;
			$sy = $height -1;
			$sw = $width;
			$sh = -$height;
		} else {
			$sx = $width - 1;
			$sy = $height - 1;
			$sw = -$width;
			$sh = -$height;
		}

		if (false === imagecopyresampled($dest, $this->_resource, 0, 0, $sx, $sy, $width, $height, $sw, $sh) ) {
			throw new RuntimeException('Image flip operation failed');
		}

		imagedestroy($this->_resource);
		$this->_resource = $dest;

		return $this;
	}

	/**
	 * @inheritdoc
	 */
	public function save($path, array $options = array())
	{
		$format = isset($options['format'])
			? $options['format']
			: pathinfo($path, PATHINFO_EXTENSION);

		$this->saveOrOutput($format, $options, $path);
		return $this;
	}

	/**
	 * @inheritdoc
	 */
	public function show($format, array $options = array())
	{
		!headers_sent() && header('Content-type:' . $this->getMimeType($format));
		$this->saveOrOutput($format, $options);
		return $this;
	}

	/**
	 * @inheritdoc
	 */
	public function thumbnail($width, $height = null, $mode = YImageInterface::THUMBNAIL_INSET)
	{
		if (empty($width) && empty($height)) {
			throw new InvalidArgumentException('Image thumbnail width and height invalid');
		}

		$imageSize = $this->getSize();
		if (!empty($width) && '%' === substr($width, -1)) {
			$width = round($imageSize[0] * (substr($width, 0, -1) / 100));
		}

		if (!empty($height) && '%' === substr($height, -1)) {
			$height = round($imageSize[1] * (substr($height, 0, -1) / 100));
		}

		if (empty($width)) {
			$width = round($height / $imageSize[1] * $imageSize[0]);
		}

		if (empty($height)) {
			$height = round($width / $imageSize[0] * $imageSize[1]);
		}

		$ratios = array(
			$width / $imageSize[0],
			$height / $imageSize[1]
		);

		$thumbnail = $this->copy();

		if ($width > $imageSize[0] && $height > $imageSize[1]) {
			return $thumbnail;
		}

		if ($mode === YImageInterface::THUMBNAIL_INSET) {
			$ratio = min($ratios);
		} else {
			$ratio = max($ratios);
		}

		if ($mode === YImageInterface::THUMBNAIL_OUTBOUND) {
			if ($imageSize[0] <= $width || $imageSize[1] <= $height) {
				$width = min($imageSize[0], $width);
				$height = min($imageSize[1], $height);
			} else {
				$thumbnailSize = $thumbnail->getSize();
				$imageSize = array(
					round($thumbnailSize[0] * $ratio),
					round($thumbnailSize[1] * $ratio)
				);
				$thumbnail->resize($imageSize[0], $imageSize[1]);
			}
			$thumbnail->crop($width, $height,
				max(0, round(($imageSize[0] - $width) / 2)),
				max(0, round(($imageSize[1] - $height) / 2))
			);
		} else {
			if ($imageSize[0] <= $width || $imageSize[1] <= $height) {
				$imageSize = array(
					round($imageSize[0] * $ratio),
					round($imageSize[1] * $ratio)
				);
				$thumbnail->resize($imageSize[0], $imageSize[1]);
			} else {
				$thumbnailSize = $thumbnail->getSize();
				$imageSize = array(
					round($thumbnailSize[0] * $ratio),
					round($thumbnailSize[1] * $ratio)
				);
				$thumbnail->resize($imageSize[0], $imageSize[1]);
			}
		}

		return $thumbnail;
	}

	public function paste(YImageInterface $image, $position)
	{
		list($width, $height) = $this->getSize();
		$size = $image->getSize();

		if (!isset($position['x'], $position['y'])) {
			if (isset($position['top'])) {
				$start['y'] = $position['top'];
			} elseif (isset($position['bottom'])) {
				$start['y'] = $height - $position['bottom'] - $size[1];
			}

			if (isset($position['left'])) {
				$start['x'] = $position['left'];
			}elseif (isset($position['right'])) {
				$start['x'] = $width - $position['right'] - $size[0];
			}
		}

		if (!isset($start['x'], $start['y'])) {
			$start = array(
				'x' => 0,
				'y' => 0
			);
		}

		if (($start['x'] + $size[0]) > $width || ($start['y'] + $size[1]) > $height) {
			throw new CException(
				'Cannot paste image of the given size at the specified '. 'position, as it moves outside of the current image\'s box'
			);
		}

		imagealphablending($this->_resource, true);
		imagealphablending($image->_resource, true);

		if (false === imagecopy($this->_resource, $image->_resource, $start['x'], $start['y'],
				0, 0, $size[0], $size[1])) {
			throw new RuntimeException('Image paste operation failed');
		}

		imagealphablending($this->_resource, false);
		imagealphablending($image->_resource, false);

		return $this;
	}

	/**
	 * 通过字符串计算xy轴坐标
	 *
	 * @param $geometry
	 * @return mixed
	 */
	protected function sanitizeGeometry($geometry)
	{
		list($width, $height) = $this->getSize();

		$geometry['width'] = min($geometry['width'], $width);
		$geometry['height'] = min($geometry['height'], $height);

		if (!isset($geometry['top'])) {
			$geometry['top'] = 'top';
		}

		if ('center' === $geometry['top']) {
			$geometry['top'] = floor(($height / 2) - ($geometry['height'] / 2));
		} elseif ('top' === $geometry['top']) {
			$geometry['top'] = 0;
		} elseif ('bottom' === $geometry['top']) {
			$geometry['top'] = $height - $geometry['height'];
		}

		if (!isset($geometry['left'])) {
			$geometry['left'] = 0;
		}

		if ('center' === $geometry['left']) {
			$geometry['left'] = floor(($width / 2) - ($geometry['width'] / 2));
		} elseif ('left' === $geometry['left']) {
			$geometry['left'] = 0;
		} elseif ('right' === $geometry['left']) {
			$geometry['left'] = $width - $geometry['height'];
		}

		return $geometry;
	}

	/**
	 * @inheritdoc
	 */
	public function getSize()
	{
		return array(imagesx($this->_resource), imagesy($this->_resource));
	}

	/**
	 * 创建一个图像
	 *
	 * @param array $size
	 * @param $operation
	 * @return resource
	 * @throws RuntimeException
	 */
	private function createImage(array $size, $operation)
	{
		$resource = imagecreatetruecolor($size[0], $size[1]);

		if (false === $resource) {
			throw new RuntimeException('Image ' . $operation . ' failed');
		}

		if (false === imagealphablending($resource, false) ||
			false === imagesavealpha($resource, true)
		) {
			throw new RuntimeException('Image ' . $operation . ' failed');
		}

		if (function_exists('imageantialias')) {
			imageantialias($resource, true);
		}

		$transparent = imagecolorallocatealpha($resource, 255, 255, 255, 127);
		imagefill($resource, 0, 0, $transparent);
		imagecolortransparent($resource, $transparent);

		return $resource;
	}

	/**
	 * 保存或输出
	 *
	 * @param $format
	 * @param array $options
	 * @param null $filename
	 * @throws RuntimeException
	 * @throws InvalidArgumentException
	 */
	private function saveOrOutput($format, array $options, $filename = null)
	{
		if (!$this->supported($format)) {
			throw new InvalidArgumentException(sprintf(
				'Saving image in "%s" format is not supported, please use one ' .
				'of the following extension: "%s"', $format,
				implode('", "', $this->supported())
			));
		}

		$save = 'image' . $format;
		$args = array(&$this->_resource, $filename);

		if (($format === 'jpeg' || $format === 'png') &&
			isset($options['quality'])
		) {
			// Png compression quality is 0-9, so here we get the value from percent.
			// Beaware that compression level for png works the other way around.
			// For PNG 0 means no compression and 9 means highest compression level.
			if ($format === 'png') {
				$options['quality'] = round((100 - $options['quality']) * 9 / 100);
			}
			$args[] = $options['quality'];
		}

		if ($format === 'png' && isset($options['filters'])) {
			$args[] = $options['filters'];
		}

		if (($format === 'wbmp' || $format === 'xbm') &&
			isset($options['foreground'])
		) {
			$args[] = $options['foreground'];
		}

		if (false === call_user_func_array($save, $args) && YII_DEBUG) {
			throw new RuntimeException('Save operation failed');
		}
	}

	/**
	 * @param null $format
	 * @return array|bool
	 */
	private function supported(&$format = null)
	{
		$formats = array('gif', 'jpeg', 'png', 'wbmp', 'xbm');

		if (null === $format) {
			return $formats;
		}

		$format = strtolower($format);

		if ('jpg' === $format || 'pjpeg' === $format) {
			$format = 'jpeg';
		}

		return in_array($format, $formats);
	}

	/**
	 * @param $format
	 * @return mixed
	 * @throws RuntimeException
	 */
	private function getMimeType($format)
	{
		if (!$this->supported($format)) {
			throw new RuntimeException('Invalid format');
		}

		static $mimeTypes = array(
			'jpeg' => 'image/jpeg',
			'jpg' => 'image/jpeg',
			'gif' => 'image/gif',
			'png' => 'image/png',
			'wbmp' => 'image/vnd.wap.wbmp',
			'xbm' => 'image/xbm',
		);

		return $mimeTypes[$format];
	}
}